package se19team1e.dao.abstrakt.property;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import se19team1e.dao.abstrakt.generics.DataAccessObject;
import se19team1e.dao.abstrakt.generics.DataTransferObject;
import se19team1e.exception.DataAccessException;
import se19team1e.util.Constants;
import se19team1e.util.DateUtils;
import se19team1e.util.StringUtil;

/**
 * 
 * Abstract class for property that implements DAO interface
 * This is used by classes such as AccountPropertyDAO, Advertise, Company, Department or JobAdvert
 * Technique is to generic template T which extends from DataTransferObject 
 * 
 * 
 * @author Arun
 * 
 * @param <T>
 *            Java Generic used to allow reuse of code for the different DTO
 */

public abstract class PropertyDAO<T extends DataTransferObject<T>> implements
		DataAccessObject<T> {

	private Properties properties = null;
	private static final String SUFFIX = ".properties";
	private String entityProperties;

	/**
	 * Constructor method for the PropertyDAO abstract class
	 * Logs the entry into the Property DAO class
	 * 
	 * @param jobAdvertProperties
	 */
	public PropertyDAO(String jobAdvertProperties) {
		this.entityProperties = jobAdvertProperties;
		try {
			properties=new Properties();
			properties = loadProperties(jobAdvertProperties);
		} catch (DataAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Loads property file with the default class loader
	 * 
	 * @param name
	 *            of the property file
	 * @return Properties 
	 *            Properties object
	 *
	 */
	public Properties loadProperties(final String filePath)  throws DataAccessException{
		//return loadProperties(name, Thread.currentThread().getContextClassLoader());
		 FileInputStream fis;
		try {
			fis = new FileInputStream(filePath);
			 properties.load(fis);  
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new DataAccessException(e.getMessage());
		} catch (IOException e) {
			e.printStackTrace();
			throw new DataAccessException(e.getMessage());
		}
		return properties;
	      
	}

	/**
	 * Overloaded method which accepts a custom class loader
	 * 
	 * @param name
	 *            Property file name to be specified
	 * @param loader
	 *            Acceppts custom class loader and get system class loader if
	 *            the class loader is passed in as null
	 * @return Properties 
	 *            Properties object
	 */
	public Properties loadProperties(String name, ClassLoader loader) {

		if (name == null)
			throw new IllegalArgumentException(
					"No property file name specified");

		InputStream inputStream = null;

		try {
			if (loader == null)
				// get system class loader if the class loader is passed in as
				// null
				loader = ClassLoader.getSystemClassLoader();

			if (!name.endsWith(SUFFIX))
				name = name.concat(SUFFIX); // You know why. Just in case we
											// accidentally provide the entire
											// property file name

			// getResourceAsStream has a bug on some implementation
			// that caches the original file
			URL url = loader.getResource( name );
			inputStream = url.openStream();

			if (inputStream != null) {
				properties = new Properties();
				properties.load(inputStream);
			}
		} catch (Exception e) {
			e.printStackTrace();
			properties = null;
		} finally {
			closeQuietly(inputStream);
		}

		return properties;
	}

	// TODO move this to a util method
	/**
	 * Given input stream is null, to close the input stream quietly 
	 * otherwise to close and ignore exception
	 * @param inputStream
	 */
	private static void closeQuietly(InputStream inputStream) {
		if (inputStream != null) {
			try {
				inputStream.close();

			} catch (Exception exception) {
				// Just go ahead and ignore this exception
			}
		}
	}

	/**
	 * Get properties from the property file
	 * 
	 * @return Properties of the specified property file
	 */
	public Properties getProperties() {
		return properties;
	}

	/**
	 * Function to populate values into DTO. Using generic, this depends on the
	 * type
	 * 
	 * @param id
	 *            Unique Identifier of the object
	 * @param dto
	 *            DTO depends of the respective object
	 * @throws Exception
	 *             if unable to map field
	 */
	public void populateValuesIntoDTO(String id, DataTransferObject<T> dto) {

		Field fields[] = dto.getClass().getDeclaredFields();
		
		//System.out.println("@@@@@@@@@@@@@@@@@@@@ DTO :"+dto);

		for (Field field : fields) {
			field.setAccessible(true);

			//System.out.println("Mapping field : " + field.getName());
			//System.out.println("with value : " + getProperties().getProperty(field.getName() + id));
			try {

				/*
				 * if (getProperties().getProperty(field.getName()+id)==null){
				 * continue; } else
				 */if (field.getType() == boolean.class) {

					field.setBoolean(dto, new Boolean(getProperties()
							.getProperty(field.getName() + id)));
				} else if (field.getType() == int.class) {
					field.setInt(
							dto,
							Integer.parseInt(getProperties().getProperty(
									field.getName() + id)));
				} else if (field.getType() == double.class) {
					field.setDouble(
							dto,
							Double.parseDouble(getProperties().getProperty(
									field.getName() + id)));
				} else if (field.getType() == Date.class) {
					field.set(dto, DateUtils.getDateFromString(getProperties()
							.getProperty(field.getName() + id),
							Constants.DATE_FORMAT));
				} else {
					field.set(dto,
							getProperties().getProperty(field.getName() + id));
				}
			} catch (Exception e) {
				System.out.println("Unable to map field : " + field.getName());
				e.printStackTrace();
			}
		}
	}

	/**
	 * 
	 * Function to convert values in a DTO object to a properties file
	 * 
	 * @param dto
	 *            DTO object to be converted
	 * @throws Exception
	 *             if unable to map field
	 */

	public void convertDTOIntoProperties(DataTransferObject<T> dto) {

		Field dtoFields[] = dto.getClass().getDeclaredFields();

		Properties eachDTOProperties = new Properties();

		int sequence=StringUtil.isEmptyOrNull((String)dto.getId())?getNextSequence():Integer.parseInt((String)dto.getId());
		
		// update sequence if needed
		int lastSequence = getLastSequence() < sequence ? sequence : getLastSequence();
		getProperties().put(Constants.LAST_SEQUENCE, "" + lastSequence);

		for (Field field : dtoFields) {
			field.setAccessible(true);
			//System.out.println("Mapping field : " + field.getName());
			try {
				dto.setId(String.valueOf(sequence));
				//System.out.println("Key :" + field.getName() + sequence);
				//System.out.println("Value :" + field.get(dto));

				if (field.getType() == Date.class) {

					eachDTOProperties.put(field.getName() + sequence,
							DateUtils.getStringFromDate(field.get(dto),
									Constants.DATE_FORMAT));
				} else if (field.getType() == boolean.class) {
					eachDTOProperties.put(field.getName() + sequence,
							String.valueOf(field.getBoolean(dto)));
				} else if (field.getType() == int.class) {
					eachDTOProperties.put(field.getName() + sequence,
							String.valueOf(field.getInt(dto)));
				} else if (field.getType() == double.class
						|| field.getType() == Double.class) {
					eachDTOProperties.put(field.getName() + sequence,
							String.valueOf(field.getDouble(dto)));
				} else {
					eachDTOProperties.put(field.getName() + sequence,
							String.valueOf(field.get(dto)));
				}

				//System.out.println(eachDTOProperties);
				getProperties().putAll(eachDTOProperties);

			} catch (Exception e) {
				System.out.println("Unable to map field : " + field.getName());
				e.printStackTrace();
			}
		}

	}

	/**
	 * Function to write to file location named "data" for the properties file
	 * 
	 * @throws DataAccessException
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public void persist() throws DataAccessException {
		try {
			getProperties()
					.store(new FileOutputStream(entityProperties), null);
		} catch (FileNotFoundException e) {
			System.out.println("Unable to write to file " + e.getMessage());
			e.printStackTrace();
			throw new DataAccessException(e.getMessage());
		} catch (IOException e) {
			System.out.println("Unable to write to file " + e.getMessage());
			e.printStackTrace();
			throw new DataAccessException(e.getMessage());
		}

	}
/**
 * Custom iterator use in this PropertyDAO class to get the next sequence of the fields
 * @return an integer 
 */
	public int getNextSequence() {
		return (getLastSequence() + 1);
	}
	/**
	 * Custom iterator use in this PropertyDAO class to get the Last sequence of the fields
	 * @return an integer 
	 */
	public int getLastSequence() {
		int lastsequence = Integer.parseInt(getProperties().getProperty(
				Constants.LAST_SEQUENCE));
		return lastsequence;
	}

	/**
	 * 
	 * To obtain the name of the fields in a properties file. 
	 * This is found on the first line of properties file.
	 * 
	 * @return fieldList List of fields used in a properties file
	 */
	public List<String> getFields() {

		List<String> fieldList = new ArrayList<String>();

		String fields = getProperties().getProperty(Constants.FIELDS);

		if (fields != null) {

			String[] fieldArray = fields.split(",");

			for (String eachField : fieldArray) {

				fieldList.add(eachField.trim());
			}

		}
		return fieldList;
	}
/**
 * Get the name of concrete properties file
 * @return String that holds the name of properties file
 */
	public String getJobAdvertProperties() {
		return entityProperties;
	}
/**
 * To set the name of the concrete properties file
 * @param jobAdvertProperties name of the concrete implmentation such as account, advertiser,etc
 */
	public void setJobAdvertProperties(String jobAdvertProperties) {
		this.entityProperties = jobAdvertProperties;
	}
}
